/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.sensor.area;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerProvider;
import nl.cloudfarming.client.logging.AppLogFactory;
import nl.cloudfarming.client.logging.AppLogger;
import nl.cloudfarming.client.model.Plot;
import nl.cloudfarming.client.model.SensorData;
import nl.cloudfarming.client.model.SensorDataFile;
import nl.cloudfarming.client.model.SensorRun;
import nl.cloudfarming.client.sensor.GenericSensorDataLayerProvider;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;

/**
 *
 * @author Timon Veenstra
 */
//TODO [TV: make multithreaded]
public class LinkSensorToPlotTask implements Runnable {

    private static final String LOG_MESSAGE_START = "link.message.info.start";
    private static final String LOG_MESSAGE_END = "link.message.info.end";
    private static final AppLogger LOG = AppLogFactory.getLogger(LinkSensorToPlotTask.class);
    private static final String processProcessTitle = NbBundle.getMessage(LinkSensorToPlotTask.class, "link.process.title");
    private SensorAreaService service = new SensorAreaService();
    private final ExecutorService pool;
    private final ProgressHandle progressHandle = ProgressHandleFactory.createHandle(processProcessTitle);
    private int progress = 0;
    private final List<Plot> plots = service.findAllPlots();

    public LinkSensorToPlotTask() {
        //
        // half of the pool will be taken by notify jobs waiting for the real job to finish
        //
        //
        pool = Executors.newFixedThreadPool(8);
    }

    @Override
    public void run() {
        LOG.info(LOG_MESSAGE_START);

        progressHandle.start(plots.size());

        for (Plot plot : plots) {
            Future<?> f = pool.submit(new LinkPlotJob(plot));
            pool.submit(new CheckFinishedJob(f));
        }

    }

    private void signalLayers() {
        //FIXME hack to update layers loading of sensor data needs refactoring to simpler architecture
        for (LayerProvider provider : Lookup.getDefault().lookupAll(LayerProvider.class)) {
            if (provider instanceof GenericSensorDataLayerProvider) {
                for (Layer layer : ((GenericSensorDataLayerProvider) provider).getLookup().lookupAll(Layer.class)) {
                    layer.updateLayer();
                }
            }
        }
    }

    private SensorRun getSensorRun(Plot plot, SensorDataFile<?> sensorDataFile) {
        SensorRun run = service.findSensorRunForPlotAndFile(plot, sensorDataFile);
        if (run == null) {
            run = new SensorRun(sensorDataFile, plot);
            service.persist(run);
        }
        return run;
    }

    class CheckFinishedJob implements Runnable {

        private final Future<?> f;

        public CheckFinishedJob(Future<?> f) {
            this.f = f;
        }

        @Override
        public void run() {
            try {
                f.get();
                progressHandle.progress(++progress);
                if (progress >= plots.size()) {
                    progressHandle.finish();
                    signalLayers();
                    LOG.info(LOG_MESSAGE_END);
                }
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (ExecutionException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }

    class LinkPlotJob implements Runnable {

        private final Plot plot;

        public LinkPlotJob(Plot plot) {
            this.plot = plot;
        }

        @Override
        public void run() {
            int progress = 0;
            ProgressHandle progressHandle = ProgressHandleFactory.createHandle(NbBundle.getMessage(LinkSensorToPlotTask.class, "link.process.plot.title", plot.getName()));
            List<SensorData> sensorDataCache = new ArrayList<SensorData>();
            try {
                long id = plot.getId();

                GeometryFactory gf = new GeometryFactory();
                WKTReader wKTReader = new WKTReader(gf);

                Geometry plotGeom = wKTReader.read(plot.getWKT());


                List<SensorData> sensorData = service.findUnlinkedSensorData();
                progressHandle.start(sensorData.size());
                for (SensorData sd : sensorData) {
                    Geometry point = gf.createPoint(new Coordinate(sd.getLongtitude(), sd.getLatitude()));
                    //
                    // first check if pointy intersects with the plot boundingbox which is a much
                    // cheaper operation. If it does, check intersection with real plot geometry
                    //
                    if (plotGeom.getEnvelope().intersects(point) && plotGeom.intersects(point)) {
                        sd.setSensorRun(getSensorRun(plot, sd.getDataSensorFile()));
                        service.persist(sd);
                        sensorDataCache.add(sd);
                        if (sensorDataCache.size() % 100 == 0) {
                            service.persist(sensorDataCache);
                            sensorDataCache.clear();
                        }
                    }
                    progressHandle.progress(++progress);
                }
                if (sensorDataCache.size() > 0) {
                    service.persist(sensorDataCache);
                    sensorDataCache.clear();
                }
                progressHandle.finish();
            } catch (ParseException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }
}
